/**
 * File: GraphReader.java
 * Created by: mhaimel
 * Created on: Mar 9, 2010
 * CVS:  $Id: GraphReader.java 1.0 Mar 9, 2010 11:08:15 AM mhaimel Exp $
 */
package uk.ac.ebi.curtain.model.graph.io;

import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.ARC_CHAR;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.GRAPH_CHAR;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.HEADER_CHAR;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.NODE_CHAR;
import static uk.ac.ebi.curtain.model.graph.io.GraphUtil.SEP_VALUE;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang.StringUtils;

import uk.ac.ebi.curtain.model.graph.Arc;
import uk.ac.ebi.curtain.model.graph.Graph;
import uk.ac.ebi.curtain.model.graph.Node;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;

/**
 * @author mhaimel
 *
 */
public class GraphReader<N,A> {

	private final Graph<N, A> emptyGraph;
	@SuppressWarnings("unchecked")
	private final ReadMapper<Graph> graphReader;
	private final ReadMapper<Node<N, A>> nodeReader;
	private final ReadMapper<Arc<N, A>> arcReader;
	private volatile ReadMapper<N> nodeValueMapper;
	private volatile ReadMapper<A> arcValueMapper;
	private final AtomicBoolean loadArcs = new AtomicBoolean(true);
	private final AtomicBoolean strict = new AtomicBoolean(true);

	public GraphReader(Graph<N, A> emptyGraph) {
		this.emptyGraph = emptyGraph;
		this.graphReader = new GraphMapper();
		this.nodeReader = new NodeMapper<N,A>();
		this.arcReader = new ArcMapper<N, A>();
		this.nodeValueMapper = null;
		this.arcValueMapper = null;
	}	
	
	public GraphReader<N, A> setArcValueMapper(ReadMapper<A> arcValueMapper) {
		this.arcValueMapper = arcValueMapper;
		return this;
	}
	
	public GraphReader<N, A> setNodeValueMapper(ReadMapper<N> nodeValueMapper) {
		this.nodeValueMapper = nodeValueMapper;
		return this;
	}
	
	public Graph<N, A> read(File file) throws IOException{
		BufferedReader br = null;
		try {
			br = new FileIO(file).getBufferedReader();
			return read(br);
		} finally{
			FileIO.closeQuietly(br);
		}
	}
	
	public Graph<N, A> read(BufferedReader br) throws IOException {
		// comment
//		boolean readNodes = getReadNodeValues();
//		boolean readArcs = getReadArcValues();
		
		Integer nodeArrLen = 1 + this.nodeReader.getFieldCount();
		nodeArrLen += (null != nodeValueMapper)?
				this.nodeValueMapper.getFieldCount():0;
		
		Integer arcArrLen = 1+this.arcReader.getFieldCount();
		arcArrLen += (null != arcValueMapper)?
				this.arcValueMapper.getFieldCount():0;
		
		//load Graph
		String line = nextLine(br);
		Graph<N, A> graph = loadGraph(line);
		while((line = nextLine(br)) != null){
			switch (line.charAt(0)) {
			case NODE_CHAR:
				loadNode(line, graph, nodeArrLen);
				break;
			case ARC_CHAR:
				if(!this.getLoadArcs()){
					return graph;
				}
				loadArc(line, graph,arcArrLen);				
				break;
			default:
				break;
			}
		}
		return graph;
	}
	
	private void loadArc(String line, Graph<N, A> graph, Integer arrLen) {
		String[] arr = this.toArray(line, arrLen);
		Arc<N, A> arc = this.arcReader.loadValue(arr, 1, graph);
		if(null != arcValueMapper){
			A val = this.arcValueMapper.loadValue(arr, 1+this.arcReader.getFieldCount(), graph);
			arc.setValue(val);	
		}
	}

	private void loadNode(String line, Graph<N, A> graph, Integer arrLen) {
		String[] arr = this.toArray(line, arrLen);
		Node<N, A> node = this.nodeReader.loadValue(arr, 1, graph);
		if(null != nodeValueMapper){
			N val = this.nodeValueMapper.loadValue(arr, 1+this.nodeReader.getFieldCount(), graph);
			node.setValue(val);		
		}
	}

	private String nextLine(BufferedReader br) throws IOException {
		String line = StringUtils.EMPTY;
		while((line = br.readLine()) != null){
			if(line.length() > 0 && line.charAt(0) != HEADER_CHAR){
				return line;
			}
		}
		return line;
	}

	@SuppressWarnings("unchecked")
	private Graph<N, A> loadGraph(String line) {
		if(line == null || line.length() < 1 || line.charAt(0) != GRAPH_CHAR){
			throw new IllegalStateException("Graph description expected in first line instead of >" + line + "<!");
		}
		return this.graphReader.loadValue(toArray(line, this.graphReader.getFieldCount()+1), 1, emptyGraph);
	}

	private String[] toArray(String line, Integer cnt) {
		String[] split = StringUtils.splitPreserveAllTokens(line, SEP_VALUE);
		int len = split.length;
		if(!cnt.equals(len) && strict.get()){
			throw new IllegalStateException("Unexpected lenght of array: Expected " + cnt + "; actual "+len+"; from line " + line);
		}
		return split;
	}

	public void setLoadArcs(boolean b) {
		this.loadArcs.set(b);
	}
	
	public boolean getLoadArcs() {
		return loadArcs.get();
	}
	
	public void setStrict(boolean b) {
		this.strict.set(b);
	}
	
	public boolean getStrict() {
		return strict.get();
	}

//	public GraphReader<N, A> setReadNodeValues(boolean readNodes) {
//		this.readNodes.set(readNodes);
//		return this;
//	}	
//	
//	public GraphReader<N, A> setReadArcValues(boolean readArc) {
//		this.readArc.set(readArc);
//		return this;
//	}
//	
//	public boolean getReadNodeValues() {
//		return this.readNodes.get();
//	}
//	public boolean getReadArcValues() {
//		return this.readArc.get();
//	}	

//	private static class DefaultReadMapper<T> implements ReadMapper<T>{
//
//		@Override
//		public int getFieldCount() {
//			return 0;
//		}
//	
//		@Override
//		public T loadValue(String[] array, int i, Graph graph) {
//			return null;
//		}		
//	}
	
}
